package org.xlp.db.ddl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xlp.assertion.AssertUtils;
import org.xlp.db.exception.IllegalCharException;
import org.xlp.db.tableoption.annotation.XLPEntity;
import org.xlp.db.utils.XLPDBUtil;
import org.xlp.scanner.pkg.ClassPathPkgScanner;
import org.xlp.scanner.pkg.ScannerPkg;
import org.xlp.utils.XLPStringUtil;
import org.xlp.utils.collection.XLPCollectionUtil;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;

/**
 * <p>创建时间：2021年8月29日 上午12:08:14</p>
 * @author xlp
 * @version 1.0 
 * @Description table创建接口
*/
public abstract class AbstractTableCreator implements TableCreator {
    private final static Logger LOGGER = LoggerFactory.getLogger(AbstractTableCreator.class);
    /**
     * 数据库连接
     */
    protected Connection connection;

    /**
     * 存储需要创建数据库表的实体类
     */
    protected Set<Class<?>> entityClasses = new HashSet<>();

    /**
     * 包扫描器
     */
    protected ScannerPkg scannerPkg;

    /**
     * 标记创建某个数据表时，是否跳过异常，继续创建接下来的表，true：跳过，false：抛出异常, 默认跳过异常
     */
    private boolean isSkipCreateTableException = true;

    /**
     * 构造函数
     *
     * @param connection
     *            数据库连接
     * @param entityClasses
     *            需要创建数据库表的实体类集合
     * @throws NullPointerException
     *             假如参数为null，则抛出该异常
     */
    public AbstractTableCreator(Connection connection, Set<Class<?>> entityClasses) {
        this(connection);
        AssertUtils.isNotNull(entityClasses, "entityClasses parameter is null!");
        this.entityClasses = entityClasses;
    }

    /**
     * 构造函数
     *
     * @param connection
     *            数据库连接
     * @param packageNames
     *            需要创建数据库表的实体类所在的包名称
     * @throws NullPointerException
     *             假如参数为空，则抛出该异常
     * @throws TableCreateException
     *             假如通过包名或是实体类失败，则抛出该异常
     */
    public AbstractTableCreator(Connection connection, String... packageNames) {
        this(connection, null, packageNames);
    }

    /**
     * 构造函数
     *
     * @param connection
     *            数据库连接
     * @param packageNames
     *            需要创建数据库表的实体类所在的包名称
     * @param scannerPkg
     * 			   包扫描器
     * @throws NullPointerException
     *             假如参数为空，则抛出该异常
     * @throws TableCreateException
     *             假如通过包名或是实体类失败，则抛出该异常
     */
    public AbstractTableCreator(Connection connection, ScannerPkg scannerPkg, String... packageNames) {
        this(connection);
        AssertUtils.isNotNull(packageNames, "packageNames parameter is null!");
        entityClasses = fromPackageNames(packageNames);
        this.scannerPkg = scannerPkg;
    }

    /**
     * 构造函数
     *
     * @param connection
     *            数据库连接
     * @throws NullPointerException
     *             假如参数为null，则抛出该异常
     */
    public AbstractTableCreator(Connection connection) {
        AssertUtils.isNotNull(connection, "connection parameter is null!");
        this.connection = connection;
    }

    /**
     * 通过给定的包名，获取该包名下的所有实体类
     *
     * @param packageNames
     * @return
     * @throws TableCreateException
     *             假如通过包名或是实体类失败，则抛出该异常
     */
    protected Set<Class<?>> fromPackageNames(String[] packageNames) {
        Set<Class<?>> classes = new HashSet<>();
        for (String packageName : packageNames) {
            classes.addAll(fromPackageName(packageName));
        }
        return classes;
    }

    /**
     * 获取包扫描器
     * @return
     */
    protected ScannerPkg getScannerPkg(){
        return scannerPkg == null ? new ClassPathPkgScanner() : scannerPkg;
    }

    /**
     * 获取给定包名下的所有实体类
     *
     * @param packageName
     * @return
     * @throws TableCreateException
     *             假如通过包名或是实体类失败，则抛出该异常
     */
    protected Set<Class<?>> fromPackageName(String packageName) {
        Set<Class<?>> result = new HashSet<>();
        Set<Class<?>> temp = new HashSet<>();
        try {
            Set<String> set = getScannerPkg().scanner(packageName);
            for (String s : set) {
                try {
                    temp.add(Class.forName(s));
                } catch (ClassNotFoundException e) {
                    if (LOGGER.isWarnEnabled()) {
                        LOGGER.warn("加载类名为：" + s + "的类失败，自动跳过。");
                    }
                }
            }
        } catch (IOException e) {
            throw new TableCreateException("通过包名或是实体类失败！", e);
        }
        for (Class<?> class1 : temp) {
            if (class1.getAnnotation(XLPEntity.class) != null) {
                result.add(class1);
            }
        }
        return result;
    }

    /**
     * 通过实体类创建数据库表
     *
     * @param entityClass
     *            实体类
     * @throws NullPointerException
     *             假如参数为null，贼抛出该异常
     * @throws TableCreateException
     *             假如创建表过程中出现错误，则抛出该异常
     */
    @Override
    public <T> void createTableByEntityClass(Class<T> entityClass) {
        AssertUtils.isNotNull(entityClass, "entityClass parameter is null!");
        XLPEntity xlpEntity = entityClass.getAnnotation(XLPEntity.class);
        if (xlpEntity == null) {
            throw new TableCreateException("给定的对象不是实体类，缺失【XLPEntity】注解！");
        }
        String createTableSql = createTableSql(entityClass);
        if (!XLPStringUtil.isEmpty(createTableSql)) {
            Statement statement = null;
            try {
                statement = connection.createStatement();
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("正在创建【\n" + createTableSql + "\n】的数据库表。。。");
                }
                statement.execute(createTableSql);
            } catch (SQLException e) {
                throw new TableCreateException("创建statement失败，或创建数据库表【\n" + createTableSql + "\n】失败", e);
            } finally {
                try {
                    XLPDBUtil.closeStatement(statement);
                } catch (SQLException e) {
                    if (LOGGER.isErrorEnabled()) {
                        LOGGER.error("关闭statement失败！");
                    }
                }
            }
        }
    }

    /**
     * 创建表
     *
     * @throws TableCreateException
     *             假如创建表过程中出现错误，则抛出该异常
     */
    @Override
    public void createTables() {
        boolean isWarn = LOGGER.isWarnEnabled();
        if (XLPCollectionUtil.isEmpty(entityClasses)) {
            if (isWarn) {
                LOGGER.warn("没有要创建的表！");
            }
            return;
        }

        boolean isInfo = LOGGER.isInfoEnabled();
        if (isInfo) {
            LOGGER.info("开始创建数据表。。。。");
        }
        boolean isDebug = LOGGER.isDebugEnabled();
        boolean isError = LOGGER.isErrorEnabled();
        String createTableSql = XLPStringUtil.EMPTY;
        Statement statement = null;
        try {
            statement = connection.createStatement();
            for (Class<?> entityClass : entityClasses) {
                // 创建表格
                createTableSql = createTableSql(entityClass);
                if (XLPStringUtil.isEmpty(createTableSql))
                    continue;
                if (isDebug) {
                    LOGGER.debug("正在创建【\n" + createTableSql + "\n】的数据库表。。。");
                }
                if (isSkipCreateTableException) {
                    try {
                        statement.execute(createTableSql);
                    } catch (Exception e) {
                        if (isError) {
                            LOGGER.error("创建statement失败，或创建数据库表【\n" + createTableSql + "\n】失败", e);
                        }
                    }
                } else {
                    statement.execute(createTableSql);
                }
                if (isDebug) {
                    LOGGER.debug("创建【\n" + createTableSql + "\n】的数据库表已完成");
                }
            }
        } catch (SQLException e) {
            throw new TableCreateException("创建statement失败，或创建数据库表【\n" + createTableSql + "\n】失败", e);
        } finally {
            try {
                XLPDBUtil.closeStatement(statement);
            } catch (SQLException e) {
                if (isError) {
                    LOGGER.error("关闭statement失败！");
                }
            }
        }

        if (isInfo) {
            LOGGER.info("创建数据表完成。。。。");
        }
    }

    /**
     * 标记创建某个数据表时，是否跳过异常，继续创建接下来的表，true：跳过，false：抛出异常, 默认跳过异常
     *
     * @return
     */
    public boolean isSkipCreateTableException() {
        return isSkipCreateTableException;
    }

    /**
     * 标记创建某个数据表时，是否跳过异常，继续创建接下来的表，true：跳过，false：抛出异常, 默认跳过异常
     *
     * @param isSkipCreateTableException
     */
    public void setSkipCreateTableException(boolean isSkipCreateTableException) {
        this.isSkipCreateTableException = isSkipCreateTableException;
    }

    /**
     * 关闭资源
     */
    public void close() {
        try {
            XLPDBUtil.close(connection);
        } catch (SQLException e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("关闭数据库连接失败！");
            }
        }
    }

    /**
     * 校验表名或表字段名是否合法（非法字符如 空格，标点符号等或以数字开头的）
     * @param str 要校验的字符
     * @throws IllegalCharException 假如表名或表字段名包含非法字符，则抛出该异常
     */
    protected static void checkTableNameOrColumnName(String str){
        checkName(str, "表名或表字段名");
    }

    /**
     * 校验表索引名是否合法（非法字符如 空格，标点符号等或以数字开头的）
     * @param str 要校验的字符
     * @throws IllegalCharException 假如表名或表字段名包含非法字符，则抛出该异常
     */
    protected static void checkTableIndexName(String str){
        checkName(str, "表索引名称");
    }

    private static void checkName(String str, String description){
        str = XLPStringUtil.emptyTrim(str);
        if (XLPStringUtil.isEmpty(str)){
            throw new IllegalCharException(description + "不能为空！");
        }
        if (XLPStringUtil.startsWith(str,"[0-9]")){
            throw new IllegalCharException(description + "不能以数字开头！");
        }
        if (!str.matches("^[_0-9a-zA-Z\\u4E00-\\u9FA5\\uF900-\\uFA2D]+$")) {
            throw new IllegalCharException(description + "只能包含下划线，数字，字母，汉字！");
        }
    }

    /**
     * 根据实体类，创建出对应的表Sql
     * @param entityClass
     * @return
     */
    protected abstract String createTableSql(Class<?> entityClass);
}
